package com.jfinal.aceadmin.tools;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import com.jfinal.aceadmin.util.FormatUtil;

import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;

@SuppressWarnings("unchecked")
public class ModelBuilder {

	private static String tableSqlPath = System.getProperty("user.dir")
			+ "/sql/table.sql";
	private static String indexSqlPath = System.getProperty("user.dir")
			+ "/sql/index.sql";
	private static String modelsDir = System.getProperty("user.dir")
			+ "/src/com/jfinal/aceadmin/model";
	private static String modelFtl = System.getProperty("user.dir")
			+ "/WebContent/WEB-INF/templateroot";

	private static void convert() throws Exception {
		SAXReader reader = new SAXReader();
		InputStream is = ModelBuilder.class.getClassLoader()
				.getResourceAsStream("model.xml");
		Document doc = reader.read(is);
		Element root = doc.getRootElement();
		List<Element> entities = root.elements("entity");
		generateSql(entities);
		generateModel(entities);
	}

	private static void generateSql(List<Element> entities) throws Exception {
		StringBuffer tableSql = new StringBuffer();
		StringBuffer indexSql = new StringBuffer();
		for (Element entity : entities) {
			String tableName = entity.attributeValue("name");
			tableSql.append("create table ").append(tableName).append(" (\n");
			List<Element> columns = entity.elements("column");
			int size = 0;
			for (Element column : columns) {
				tableSql.append(createColumnSql(column));
				size++;
				if (size < columns.size()) {
					tableSql.append(",");
				}
				tableSql.append("\n");
			}
			tableSql.append(") ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; \n\n");
			List<Element> finders = entity.elements("finder");
			for (Element finder : finders) {
				indexSql.append(createIndexSql(finder, tableName));
				indexSql.append("\n");
			}
		}
		File tableSqlFile = new File(tableSqlPath);
		FileUtils.writeStringToFile(tableSqlFile, "");
		FileUtils.writeStringToFile(tableSqlFile, tableSql.toString());
		File indexSqlFile = new File(indexSqlPath);
		FileUtils.writeStringToFile(indexSqlFile, "");
		FileUtils.writeStringToFile(indexSqlFile, indexSql.toString());
	}

	private static String createColumnSql(Element column) {
		StringBuffer columnSql = new StringBuffer("\t");
		String name = column.attributeValue("name");
		String type = column.attributeValue("type");
		String primary = column.attributeValue("primary");
		String idType = column.attributeValue("id-type");
		String dbSize = column.attributeValue("db-size");
		String dbNotNull = column.attributeValue("db-not-null");
		String dbDefaultValue = column.attributeValue("db-default-value");

		columnSql.append(name).append(" ");
		if (type.equalsIgnoreCase("boolean")) {
			columnSql.append("TINYINT");
		} else if (type.equalsIgnoreCase("double")
				|| type.equalsIgnoreCase("float")) {
			if (StringUtils.isNotBlank(dbSize))
				columnSql.append("DOUBLE(" + dbSize + ")");
			else
				columnSql.append("DOUBLE");
		} else if (type.equalsIgnoreCase("int")
				|| type.equalsIgnoreCase("integer")
				|| type.equalsIgnoreCase("short")) {
			if (StringUtils.isNotBlank(dbSize))
				columnSql.append("INTEGER(" + dbSize + ")");
			else
				columnSql.append("INTEGER");
		} else if (type.equalsIgnoreCase("long")) {
			if (StringUtils.isNotBlank(dbSize))
				columnSql.append("BIGINT(" + dbSize + ")");
			else
				columnSql.append("BIGINT");
		} else if (type.equalsIgnoreCase("string")) {
			int maxLength = 75;
			if (dbSize != null) {
				try {
					maxLength = Integer.parseInt(dbSize);
				} catch (Exception e) {
				}
			}
			if (maxLength == -1) {
				columnSql.append("LONGTEXT");
			} else if (maxLength <= 4000) {
				columnSql.append("VARCHAR(" + maxLength + ")");
			} else if (maxLength > 4000) {
				columnSql.append("TEXT");
			}
		} else if (type.equalsIgnoreCase("date")) {
			columnSql.append("DATETIME");
		} else {
			columnSql.append("invalid");
		}
		if (StringUtils.isNotBlank(dbDefaultValue)) {
			columnSql.append(" default ");
			if (type.equalsIgnoreCase("string")) {
				columnSql.append("'");
			}
			columnSql.append(dbDefaultValue);
			if (type.equalsIgnoreCase("string")) {
				columnSql.append("'");
			}
		}

		if (StringUtils.isNotBlank(primary) && primary.equalsIgnoreCase("true")) {
			columnSql.append(" not null");
			columnSql.append(" primary key");
		}
		if (StringUtils.isNotBlank(dbNotNull)
				&& dbNotNull.equalsIgnoreCase("true")) {
			columnSql.append(" not null ");
		}
		if (StringUtils.isNotBlank(idType)) {
			if (idType.equalsIgnoreCase("identity")) {
				columnSql.append(" IDENTITY");
			}
			if (idType.equalsIgnoreCase("native")) {
				columnSql.append(" AUTO_INCREMENT");
			}
		}
		return columnSql.toString();
	}

	private static String createIndexSql(Element finder, String tableName) {
		StringBuffer indexSql = new StringBuffer("create index ");
		StringBuffer indexColumn = new StringBuffer();
		List<Element> finderColumns = finder.elements("finder-column");
		int size = 0;
		for (Element finderColumn : finderColumns) {
			String name = finderColumn.attributeValue("name");
			indexColumn.append(name);
			size++;
			if (size < finderColumns.size()) {
				indexColumn.append(", ");
			}
		}
		String uuid = UUID.randomUUID().toString();
		int length = uuid.length();
		indexSql.append("IX_").append(uuid.substring(length - 12))
				.append(" on ").append(tableName).append(" (")
				.append(indexColumn).append(");");
		return indexSql.toString();
	}

	private static void generateModel(List<Element> entities) throws Exception {
		Configuration configuration = new Configuration();
		configuration.setObjectWrapper(new DefaultObjectWrapper());
		configuration.setDirectoryForTemplateLoading(new File(modelFtl));
		Template template = configuration.getTemplate("model.ftl");
		Template template2 = configuration.getTemplate("model_impl.ftl");
		for (Element entity : entities) {
			Model model = new Model();
			String name = entity.attributeValue("name");
			model.setName(name);
			List<Element> columns = entity.elements("column");
			List<Column> _columns = new ArrayList<Column>();
			for (Element column : columns) {
				String columnName = column.attributeValue("name");
				String columnType = column.attributeValue("type");
				String pk = column.attributeValue("primary");
				if (StringUtils.isNotBlank(pk) && pk.equalsIgnoreCase("true")) {
					model.setPk(columnName);
				}
				Column _column = new Column();
				_column.setName(columnName);
				_column.setType(columnType);
				_columns.add(_column);
				if (columnType.equalsIgnoreCase("date")) {
					model.setHasDateColumn(true);
				}
			}
			model.setColumns(_columns);
			List<Element> finders = entity.elements("finder");
			List<Finder> _finders = new ArrayList<Finder>();
			for (Element finder : finders) {
				Finder _finder = new Finder();
				String returnType = finder.attributeValue("return-type");
				if (returnType.equalsIgnoreCase("collection")) {
					model.setHasListImport(true);
				}
				_finder.setCollection(returnType.equalsIgnoreCase("collection"));
				String finderName = finder.attributeValue("name");
				_finder.setName(finderName);
				List<Element> finderOrders = finder.elements("finder-order");
				List<OrderColumn> orderColumns = new ArrayList<OrderColumn>();
				if (!finderOrders.isEmpty()) {
					for (Element finderOrder : finderOrders) {
						OrderColumn orderColumn = new OrderColumn();
						String oName = finderOrder.attributeValue("name");
						String orderBy = finderOrder.attributeValue("by");
						orderColumn.setName(oName);
						orderColumn.setOrderBy(orderBy);
						orderColumns.add(orderColumn);
					}
				}
				_finder.setOrderColumns(orderColumns);
				List<Element> finderColumns = finder.elements("finder-column");
				List<Column> _finderColumns = new ArrayList<Column>();
				for (Element finderColumn : finderColumns) {
					String columnName = finderColumn.attributeValue("name");
					for (Column _column : _columns) {
						if (columnName.equals(_column.getName())) {
							_finderColumns.add(_column);
							break;
						}
					}
				}
				_finder.setFinderColumns(_finderColumns);
				_finders.add(_finder);
			}
			model.setFinders(_finders);
			generateModelFile(template, model);
			generateModelImplFile(template2, model);
		}
	}

	private static void generateModelImplFile(Template template, Model model)
			throws TemplateException, IOException {
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("model", model);
		StringWriter writer = new StringWriter();
		template.process(map, writer);
		String content = writer.toString();
		File modelImplFile = new File(modelsDir + "/" + model.getName()
				+ "Impl.java");
		if (!modelImplFile.exists()) {
			FileUtils.writeStringToFile(modelImplFile, "");
			FileUtils.writeStringToFile(modelImplFile, content);
		}
	}

	private static void generateModelFile(Template template, Model model)
			throws TemplateException, IOException {
		System.out.println("Model : " + model);
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("model", model);
		StringWriter writer = new StringWriter();
		template.process(map, writer);
		String content = writer.toString();
		File modelFile = new File(modelsDir + "/" + model.getName() + ".java");
		FileUtils.writeStringToFile(modelFile, "");
		FileUtils.writeStringToFile(modelFile, content);
		FormatUtil.format(modelFile);
	}

	public static void main(String[] args) throws Exception {
		convert();
	}

}
